﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Modelo
{
    [Serializable]
    public abstract class Personaje : IColisionable
    {
        protected int radio; //va a ser modelado como un circulo y el radio es medido desde el centro al borde
        protected Posicion posicion;
        public int velActual { get; set; }
        public Mapa mapa { get; set; }
        protected Type bomba;
        protected int nivel;

        public Personaje(Posicion posicionInicial, int tamanio, Mapa mapaCreador)
        {
            //El personaje es circular para facilitar el calculo de distancias.
            radio = tamanio;
            posicion = posicionInicial;
            velActual = 5;
            mapa = mapaCreador;
            this.nivel = 1;
        }

        #region IColisionable Members

        public int getTamanio()
        {
            return radio;
        }

        public int getPosX()
        {
            return posicion.getX();
        }

        public int getPosY()
        {
            return posicion.getY();
        }

        public abstract void recibirColision(IColisionable elOtro);

        public abstract void manejarColisionCon(Bombita elOtro);

        public abstract void manejarColisionCon(Enemigo elOtro);

        public abstract void manejarColisionCon(PowerUp elOtro);

        public abstract void manejarColisionCon(Bomba elOtro);

        #endregion

        #region Movimiento

        public virtual void mover(int dirx, int diry)
        {
            //Las dir son -1, 1 o 0. Solo indican el sentido en que se esta moviendo.
            int nuevoX = posicion.getX();
            int nuevoY = posicion.getY();
            //Si me estoy moviendo para arriba:
            if (diry == -1)
            {
                if (cornerSupIzqCaminable(new Posicion(posicion.getX(), posicion.getY() + velActual * diry)) && cornerSupDerCaminable(new Posicion(posicion.getX(), posicion.getY() + velActual * diry)))
                {
                    nuevoY += velActual * diry;
                }
                else
                {
                    //Lo pongo contra la pared de arriba.
                    int celdaY = posicion.getY() / mapa.getTamCelda();
                    nuevoY = celdaY * mapa.getTamCelda() + radio;
                }
            }
            //Si me estoy moviendo para abajo:
            else if (diry == 1)
            {
                if (cornerInfIzqCaminable(new Posicion(posicion.getX(), posicion.getY() + velActual * diry)) && cornerInfDerCaminable(new Posicion(posicion.getX(), posicion.getY() + velActual * diry)))
                {
                    nuevoY += velActual * diry;
                }
                else
                {
                    //Lo pongo contra la pared de abajo.
                    int celdaY = posicion.getY() / mapa.getTamCelda();
                    nuevoY = (celdaY + 1) * mapa.getTamCelda() - radio;
                }
            }
            //Si me estoy moviendo para la izquierda:
            else if (dirx == -1)
            {
                if (cornerInfIzqCaminable(new Posicion(posicion.getX() + velActual * dirx, posicion.getY())) && cornerSupIzqCaminable(new Posicion(posicion.getX() + velActual * dirx, posicion.getY())))
                {
                    nuevoX += velActual * dirx;
                }
                else
                {
                    //Lo pongo contra la pared de la izquierda
                    int celdaX = posicion.getX() / mapa.getTamCelda();
                    nuevoX = celdaX * mapa.getTamCelda() + radio;
                }
            }
            //Si me estoy moviendo para la derecha:
            else if (dirx == 1)
            {
                if (cornerInfDerCaminable(new Posicion(posicion.getX() + velActual * dirx, posicion.getY())) && cornerSupDerCaminable(new Posicion(posicion.getX() + velActual * dirx, posicion.getY())))
                {
                    nuevoX += velActual * dirx;
                }
                else
                {
                    //Lo pongo contra la pared de la derecha
                    int celdaX = posicion.getX() / mapa.getTamCelda();
                    nuevoX = (celdaX + 1) * mapa.getTamCelda() - radio;
                }
            }
            posicion = new Posicion(nuevoX, nuevoY);
            if (mapa.encontroSalida() && this.GetType() == typeof(Bombita))
            {
                int a = 0;
                //paso de nivel
            }
            return;
        }

        protected virtual bool cornerInfIzqCaminable(Posicion pos)
        {
            //Devuelve true o false de acuerdo a si el corner inferior izquierdo de la figura es caminable en la posicion x e y 
            //recibida es caminable o no.

            int downY = (int)Math.Floor((double)(pos.getY() + radio - 1) / mapa.getTamCelda());
            int izqX = (int)Math.Floor((double)(pos.getX() - radio) / mapa.getTamCelda());
            //Me fijo de estar dentro de los limites del mapa.
            if (downY < 0 || downY >= mapa.obtenerCeldasY()) return false;
            if (izqX < 0 || izqX >= mapa.obtenerCeldasX()) return false;
            //Finalmente me fijo si esa celda es caminable.
            return mapa.celdaCaminable(izqX, downY);
        }

        protected virtual bool cornerInfDerCaminable(Posicion pos)
        {
            //Devuelve true o false de acuerdo a si el corner inferior izquierdo de la figura es caminable en la posicion x e y 
            //recibida es caminable o no.
            int downY = (int)Math.Floor((double)(pos.getY() + radio - 1) / mapa.getTamCelda());
            int derX = (int)Math.Floor((double)(pos.getX() + radio - 1) / mapa.getTamCelda());
            //Me fijo de estar dentro de los limites del mapa.
            if (downY < 0 || downY >= mapa.obtenerCeldasY()) return false;
            if (derX < 0 || derX >= mapa.obtenerCeldasX()) return false;
            //Finalmente me fijo si esa celda es caminable.
            return mapa.celdaCaminable(derX, downY);
        }

        protected virtual bool cornerSupDerCaminable(Posicion pos)
        {
            //Devuelve true o false de acuerdo a si el corner superior derecho de la figura es caminable en la posicion x e y 
            //recibida es caminable o no.
            int upY = (int)Math.Floor((double)(pos.getY() - radio) / mapa.getTamCelda());
            int derX = (int)Math.Floor((double)(pos.getX() + radio - 1) / mapa.getTamCelda());
            //Me fijo de estar dentro de los limites del mapa.
            if (upY < 0 || upY >= mapa.obtenerCeldasY()) return false;
            if (derX < 0 || derX >= mapa.obtenerCeldasX()) return false;
            //Finalmente me fijo si esa celda es caminable.
            return mapa.celdaCaminable(derX, upY);
        }

        protected virtual bool cornerSupIzqCaminable(Posicion pos)
        {
            //Devuelve true o false de acuerdo a si el corner superior izquierdo de la figura es caminable en la posicion x e y 
            //recibida es caminable o no.
            int upY = (int)Math.Floor((double)(pos.getY() - radio) / mapa.getTamCelda());
            int izqX = (int)Math.Floor((double)(pos.getX() - radio) / mapa.getTamCelda());
            //Me fijo de estar dentro de los limites del mapa.
            if (upY < 0 || upY >= mapa.obtenerCeldasY()) return false;
            if (izqX < 0 || izqX >= mapa.obtenerCeldasX()) return false;
            //Finalmente me fijo si esa celda es caminable.
            return mapa.celdaCaminable(izqX, upY);
        }

        public void aumentarVelocidad(int aumento)
        {
            velActual += velActual * aumento / 100;
        }

        #endregion

        public virtual void plantarBomba()
        {
            Posicion posicionBomba = mapa.obtenerPosicionCentroDeCelda(this.posicion);
            Type[] tiposParametro = { typeof(Posicion), typeof(Mapa) };
            Object[] parametros = { (Object)posicionBomba, (Object)this.mapa };
            Bomba nuevaBomba = (Bomba)bomba.GetConstructor(tiposParametro).Invoke(parametros);
            mapa.agregarObstaculo(nuevaBomba);
        }
    }
}
